﻿using System;
using System.IO;
using System.Reflection;
using System.Threading;
using log4net;
using log4net.Config;

namespace Elderos.Utils.Logging
{
	public static class Logger
	{
		private static readonly ILog Lg = LogManager.GetLogger(typeof(Logger));

		#region Initialization

		private const string ConfigPath = "log4net.config";

		private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();

		private static bool _initialized;

		/// <summary>
		/// Logger initialize
		/// </summary>
		/// <param name="configPath">The path to the log4net configuration file.
		/// By default "log4net.config".
		/// </param>
		private static void Initialize(string configPath = ConfigPath)
		{
			if (_initialized) return;
			if (File.Exists(configPath))
				XmlConfigurator.Configure(new FileInfo(configPath));
			else
				XmlConfigurator.Configure();
			Thread.MemoryBarrier();
			_initialized = true;
		}

		/// <summary>
		/// Reinitializes the logger.
		/// </summary>
		/// <param name="configPath">The path to the log4net configuration file.
		/// By default "log4net.config".</param>
		public static void Reinitialize(string configPath = ConfigPath)
		{
			Locker.EnterWriteLock();
			try
			{
				if (_initialized) _initialized = false;
				Initialize();
			}
			finally
			{
				Locker.ExitWriteLock();
			}
		}

		private static void EnsureInitialization()
		{
			Locker.EnterUpgradeableReadLock();
			try
			{
				if (!_initialized)
				{
					Locker.EnterWriteLock();
					Initialize();
					Locker.ExitWriteLock();
				}
			}
			finally
			{
				Locker.ExitUpgradeableReadLock();
			}
		}

		#endregion

		#region Logging

		//Debug
		public static void Debug(string message)
		{
			EnsureInitialization();
			Lg.Debug(message);
		}

		public static void Debug(string message, Exception ex)
		{
			EnsureInitialization();
			Lg.Debug(message, ex);
		}

		public static void Debug(Exception ex)
		{
			EnsureInitialization();
			Debug(ex.Message, ex);
		}

		public static void Debug(string format, params object[] args)
		{
			EnsureInitialization();
			Lg.DebugFormat(format, args);
		}

		public static void Debug(IFormatProvider provider, string format, params object[] args)
		{
			EnsureInitialization();
			Lg.DebugFormat(provider, format, args);
		}

		//Info
		public static void Info(string message)
		{
			EnsureInitialization();
			Lg.Info(message);
		}

		public static void Info(string message, Exception ex)
		{
			EnsureInitialization();
			Lg.Info(message, ex);
		}

		public static void Info(Exception ex)
		{
			EnsureInitialization();
			Info(ex.Message, ex);
		}

		public static void Info(string format, params object[] args)
		{
			EnsureInitialization();
			Lg.InfoFormat(format, args);
		}

		public static void Info(IFormatProvider provider, string format, params object[] args)
		{
			EnsureInitialization();
			Lg.InfoFormat(provider, format, args);
		}

		//Error
		public static void Error(string message)
		{
			EnsureInitialization();
			Lg.Error(message);
		}

		public static void Error(string message, Exception ex)
		{
			EnsureInitialization();
			Lg.Error(message, ex);
		}

		public static void Error(Exception ex)
		{
			EnsureInitialization();
			Error(ex.Message, ex);
		}

		public static void Error(string format, params object[] args)
		{
			EnsureInitialization();
			Lg.ErrorFormat(format, args);
		}

		public static void Error(IFormatProvider provider, string format, params object[] args)
		{
			EnsureInitialization();
			Lg.ErrorFormat(provider, format, args);
		}

		//Warn
		public static void Warn(string message)
		{
			EnsureInitialization();
			Lg.Warn(message);
		}

		public static void Warn(string message, Exception ex)
		{
			EnsureInitialization();
			Lg.Warn(message, ex);
		}

		public static void Warn(Exception ex)
		{
			EnsureInitialization();
			Warn(ex.Message, ex);
		}

		public static void Warn(string format, params object[] args)
		{
			EnsureInitialization();
			Lg.WarnFormat(format, args);
		}

		public static void Warn(IFormatProvider provider, string format, params object[] args)
		{
			EnsureInitialization();
			Lg.WarnFormat(provider, format, args);
		}

		//Fatal
		public static void Fatal(string message)
		{
			EnsureInitialization();
			Lg.Fatal(message);
		}

		public static void Fatal(string message, Exception ex)
		{
			EnsureInitialization();
			Lg.Fatal(message, ex);
		}

		public static void Fatal(Exception ex)
		{
			EnsureInitialization();
			Fatal(ex.Message, ex);
		}

		public static void Fatal(string format, params object[] args)
		{
			EnsureInitialization();
			Lg.FatalFormat(format, args);
		}

		public static void Fatal(IFormatProvider provider, string format, params object[] args)
		{
			EnsureInitialization();
			Lg.FatalFormat(provider, format, args);
		}

		#endregion

		#region Asserts

		public static void Assert(Func<bool> assertion, string message,
								  Type exception = null)
		{
			if (assertion.Invoke()) return;
			if (exception == null)
			{
				Error(message);
				return;
			}
			ConstructorInfo ctor = exception.GetConstructor(new[] {typeof(string)});
			if (ctor == null) return;
			var ex = (Exception)ctor.Invoke(new object[] {message});
			throw ex;
		}

        public static void Assert<T>(Func<bool> assertion, string message)
            where T : Exception
        {
            if (assertion.Invoke()) return;
            
            ConstructorInfo ctor = typeof(T).GetConstructor(new[] { typeof(string) });
            if (ctor == null) return;
            var ex = (Exception)ctor.Invoke(new object[] { message });
            throw ex;
        }

		#endregion
	}
}